﻿using System;
using System.Security.Cryptography;
using System.Text;

namespace Reece.Open.Mvc.Atom.Extenders
{
    /// <summary>
    /// Guid extenders
    /// http://www.ietf.org/rfc/rfc4122.txt
    /// https://github.com/LogosBible/Logos.Utility/blob/master/src/Logos.Utility/GuidUtility.cs#L98
    /// </summary>
    public static class GuidExtenders
    {
        /// <summary>
        /// Creates a version 3 or 5 UUID for a named resource within a namespace.
        /// </summary>
        /// <param name="name">The name to generate the id for.</param>
        /// <param name="nameSpace">The namespace in which the name exists.</param>
        /// <param name="versionNumber">The version number of UUID to generate.</param>
        /// <returns>An RFC 4122 compliant UUID.</returns>
        public static Guid ToUrnUuid(this string name, Guid nameSpace, int versionNumber = 5)
        {
            byte[] nameBytes = Encoding.UTF8.GetBytes(name);
            byte[] nameSpaceBytes = nameSpace.SwitchByteOrder().ToByteArray();

            using ( var hash = GetHash(versionNumber))
            {
                hash.TransformBlock(nameSpaceBytes, 0, nameSpaceBytes.Length, null, 0);
                hash.TransformFinalBlock(nameBytes, 0, nameBytes.Length);

                byte[] uuidBytes = new byte[16];
                Array.Copy(hash.Hash,uuidBytes,16);

                // RFC 4122 - time-high and version
                uuidBytes[6] = (byte) ((uuidBytes[6] & 0x0f) | (versionNumber << 4));

                // Fixed 0 and 1 bits in clock_seq_hi_and_reserved
                uuidBytes[8] = (byte) ((uuidBytes[8] & 0x3f) | 0x80);

                return (new Guid(uuidBytes)).SwitchByteOrder();
            }
        }

        private static HashAlgorithm GetHash(int uuidVersionNumber )
        {
            switch ( uuidVersionNumber )
            {
                case 3:
                    return MD5.Create();
                case 5:
                    return SHA1.Create();
            }
            throw new ArgumentException("Invalid UUID version number, must be 3 or 5.");
        }

        /// <summary>
        /// Switches the byte order of the first 8 bytes of a UUID.
        /// This is for converting a little-endian Guid to a network byte ordered uuid and back.
        /// </summary>
        /// <param name="input">Source guid.</param>
        /// <returns>A new Guid with byte order of the parts swapped.</returns>
        public static Guid SwitchByteOrder(this Guid input)
        {
            // First 8 bytes are little-endian and the last 8 are big-endian
            byte[] bytes = input.ToByteArray();

            // RFC 4122
            // time-low = 4hexOctet (swap 0, 3 / 1, 2)
            bytes.Swap(0, 3).Swap(1, 2);

            // time-mid = 2hexOctet (swap 4, 5)
            bytes.Swap(4, 5);

            // time-high and version = 2hex Octet (swap 6, 7)
            bytes.Swap(6, 7);

            return new Guid(bytes);
        }

        private static byte[] Swap(this byte[] input, int lindex, int rindex )
        {
            byte lvalue = input[lindex];
            input[lindex] = input[rindex];
            input[rindex] = lvalue;
            return input;
        }
    }

}
